package org.threads.orderedthreads;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.utils.Utils;

/**
 * Given M threads, which are numbered from 1 to M. Each of
 * the output stream takes turns in a circle in the prescribed manner
 * (from largest to smallest or from smallest to largest).
 * Time of each flow inferred. Once the flow of the exhaust,
 * it goes into a state of wait, and starts running the next thread,
 * according to the established order.
 */
public class OrderedThread extends Thread {

    private static final Logger LOGGER = LoggerFactory.getLogger(OrderedThread.class);

    private static final int MIN_SLEEP_TIME = 200;
    private static final int MAX_SLEEP_TIME = 1000;

    private static int lastNumber = 1;
    private static boolean isAscending = true;

    private static int lastOut = -1;
    private static int criteria;
    private final Object lock;
    private int number;

    /**
     * Common constructor.
     * @param lock object for synchronizing
     */
    public OrderedThread(final Object lock) {

        this.lock = lock;

        number = lastNumber++;

        if (lastOut == -1) {
            if (isAscending) {
                criteria = 1;
                lastOut = 0;
            } else {
                criteria = -1;
                lastOut = lastNumber;
            }
        }
    }

    /**
     * Entry point, used for testing.
     * @param args
     */
    public static void main(String[] args) {

        final Object lock = new Object();

        for (int i = 1; i < 6; i++) {
            new OrderedThread(lock).start();
        }
    }

    @Override
    public void run() {

        while (true) {

            synchronized (lock) {

                while (number - lastOut != criteria) {
                    try {
                        lock.wait();
                    } catch (InterruptedException e) {
                        LOGGER.error(e.toString());
                    }
                }

                LOGGER.info(number + " ");
                setLastOut(number);

                try {
                    sleep(Utils.getRandom(MIN_SLEEP_TIME, MAX_SLEEP_TIME));
                } catch (InterruptedException e) {
                    LOGGER.error(e.toString());
                }

                lock.notifyAll();
            }
        }
    }

    private void setLastOut(int lastOut) {

        if (isAscending && (lastOut == lastNumber - 1)) {
            OrderedThread.lastOut = 0;
        } else if (!isAscending && lastOut == 1) {
            OrderedThread.lastOut = lastNumber;
        } else {
            OrderedThread.lastOut = lastOut;
        }
    }
}
